
/**
 * StorageUtil游戏数据设计及读写中间件
 * https://huangx916.github.io/2019/03/16/dataMiddleware/
 */

import { DEBUG, EDITOR } from "cc/env";
import c3d from "../cc";
import { BitEncrypt } from "../libs/BitEncrypt";

type T_RefData = Object | Map<any, any> | Set<any> | Array<any>;
type T_BaseData = number | boolean | string;

type StorageVauleType = "number" | "string" | "boolean" | "object";
interface StorageData {
    type: StorageVauleType,
    value: string | number | boolean | object;
}
interface I_DataRef {
    /**引用对象-guid的键值对 */
    _anyRefMap: Map<T_RefData, string>;
    /**guid-引用对象的键值对 */
    _anyGuidMap: Map<string, T_RefData>;
    /**存储引用类型的键值对 */
    _anyTypeMap: Map<string, string>;
}
export class StorageUtil {
    /**游戏名字 */
    private static gameName = "tongLin";

    /**数据对象 */
    private static pureDataCache = {};  // 只包含成员变量，不包含成员函数
    /**数据引用列表 */
    private static gameDataRef = {} as { [key: string]: I_DataRef };


    private static keyMap = {}; // 需存储的已改变的数据
    private static intervalId = null;

    /**存储时间间隔计时器的map */
    private static intervalIdMap: Map<string, number> = new Map();

    /**默认的数据存储同步间隔(单位毫秒)-->延迟存储的 */
    private static syncDefaultLocalDataInterval = 500;

    //#region 
    /**
     * 延迟存储接口
     * [每隔一段时间(默认ms)进行存储]
     * @param key 存储的索引
     * @param value 数据
     * @param interval 存储时间
     */
    // static setLocalItemDefer(key: string, value, interval = this.syncDefaultLocalDataInterval) {
    //     // 过滤掉函数字段并断开引用关系
    //     let cloneValue = { ...value };

    //     let _syncLocalDataInterval = () => {
    //         if (!this.intervalId) {
    //             let id = setInterval(() => {
    //                 for (let uniKey in this.keyMap) {
    //                     let keysObj = this.keyMap[uniKey];
    //                     let key = keysObj["key"];
    //                     let subKey = keysObj["subKey"];
    //                     if (!subKey) {
    //                         this._setData(uniKey, this.gameDataRef[key]);
    //                     }
    //                     else {
    //                         this._setData(uniKey, this.gameDataRef[key][subKey]);
    //                     }
    //                 }
    //                 this.keyMap = {};
    //             }, interval);
    //             this.intervalIdMap.set(key, id);
    //         }
    //     }

    //     if (typeof cloneValue === "object") {
    //         for (let subKey in cloneValue) {
    //             if (subKey === "_storageKey") {
    //                 continue;
    //             }
    //             let subValue = cloneValue[subKey];
    //             if (this.pureDataCache[key]) {
    //                 if (JSON.stringify(this.pureDataCache[key][subKey]) !== JSON.stringify(subValue)) {
    //                     this.keyMap[key + subKey] = { "key": key, "subKey": subKey };
    //                     _syncLocalDataInterval();
    //                 }
    //             }
    //             else {
    //                 this.keyMap[key + subKey] = { "key": key, "subKey": subKey };
    //                 _syncLocalDataInterval();
    //             }
    //         }
    //     }
    //     else {
    //         if (JSON.stringify(this.pureDataCache[key]) !== JSON.stringify(cloneValue)) {
    //             this.keyMap[key] = { "key": key, "subKey": null };
    //             _syncLocalDataInterval();
    //         }
    //     }

    //     this.pureDataCache[key] = cloneValue;
    // }
    //#endregion

    /**
     * 立即存储接口
     * @param key 索引
     * @param value 数据
     */
    static setLocalItemImmediately(key: string, value) {
        let str = this.stringify(value, this.getRefMapList(key));
        this._setData(key, str);
    }

    /**
     * 获取本地存储数据
     * @param key 索引
     * @param defaultValue 数据
     */
    static getLocalItem(key: string, defaultValue?): any {
        let value = this._getData(key, defaultValue);
        value = this.parseData(value, this.getRefMapList(key));
        //保存解析的对象引用，防止再次存储时被覆盖
        this.pureDataCache[key] = value;
        return value;
    }

    /**
     * 获取对象引用map列表
     * @param key key
     * @returns 
     */
    static getRefMapList(key: string) {
        let refMapList = this.gameDataRef[key];
        if (!refMapList) {
            refMapList = {
                _anyGuidMap: new Map(),
                _anyRefMap: new Map(),
                _anyTypeMap: new Map(),
            };
            this.gameDataRef[key] = refMapList;
        }
        return refMapList;
    }

    /**
     * 获取游戏内存数据
     * @param key 索引
     */
    static getGameDataItem(key) {
        return this.pureDataCache[key];
    }

    /**
     * 查找是否有指定的key
     * @param key 查询的key
     */
    static hasLocalItem(key: string) {
        let value = this._getData(key);
        return value ? true : false;
    }

    /**
     * 根据key存储数据
     * @param key 要存储的key
     * @param value 数据
     */
    private static _setData(key: string, value) {
        c3d.sys.localStorage.setItem(key, value);
    }

    /**
     * 根据key获取指定的数据
     * @param key 获取的key
     * @param defaultValue 默认值
     * @returns 数据
     */
    private static _getData(key: string, defaultValue?: any) {
        let ret = c3d.sys.localStorage.getItem(key);
        if ((ret == null || ret == "null") && defaultValue != null) {
            ret = defaultValue;
        }
        return ret;
    }

    /**
     * 根据key 存储指定的数据
     * @param key 存储数据的key
     * @param data 数据
     */
    private static stringify(data: any, refMapList: I_DataRef) {
        let stringifyRef = (stringifyData: T_RefData | T_BaseData, isUpdateData = false) => {
            try {
                let typeStr = this.getVarType(stringifyData);
                let vType_is = this.transTypeToStr(typeStr);

                if (vType_is.object || vType_is.Array || vType_is.map || vType_is.set) {
                    //解析引用对象
                    let refGuid = refMapList._anyRefMap.get(stringifyData);
                    if (!refGuid || isUpdateData) {
                        //没有引用,创建一个存储引用的guid
                        if (!isUpdateData)
                            refGuid = this.guid();

                        //存储引用的值
                        refMapList._anyRefMap.set(stringifyData, refGuid);
                        let refStr: string;
                        if (vType_is.map || vType_is.set) {
                            let stringifyMap = stringifyData as Map<string, any>;
                            for (let [key, child] of stringifyMap)
                                stringifyMap.set(key, stringifyRef(child));

                            refStr = JSON.stringify([...stringifyData as any]);
                        } else {
                            for (let [key, child] of Object.entries(stringifyData))
                                stringifyData[key] = stringifyRef(child);
                            refStr = JSON.stringify(stringifyData);
                        }

                        //存储引用对象和引用对象的类型
                        refMapList._anyGuidMap.set(refGuid, refStr);
                        refMapList._anyTypeMap.set(refGuid, typeStr);
                    }

                    return refGuid;
                } else
                    return stringifyData;
            } catch (err) {
                c3d.error(`StorageUtil.转换出错err:`);
                c3d.error(err);
            }
        };
        let dataType_is = this.is(data);
        if (dataType_is.map || dataType_is.Array || dataType_is.object || dataType_is.set) {
            //获取没有相互引用的json字符,并存储
            let saveDataStr = JSON.stringify(stringifyRef(data));
            //更新引用字符
            refMapList._anyGuidMap.forEach(ref => stringifyRef(ref, true));
            //获取与引用相关的json字符，并存储
            let saveRefStr = JSON.stringify([...refMapList._anyGuidMap]);
            //存储引用对象的类型
            let saveRefTypeStr = JSON.stringify([...refMapList._anyTypeMap]);
            //存储所有的字符
            let saveStr = JSON.stringify([saveDataStr, saveRefStr, saveRefTypeStr]);
            //恢复相互引用
            refMapList._anyRefMap.forEach((key, ref) => refMapList._anyGuidMap.set(key, ref));
            return saveStr;
        }
        return data;
    }

    /**
     * 根据key获取压缩存储的数据
     * @param key 要解压的数据key
     * @param parseData 要解析的字符
     * @returns 数据
     */
    private static parseData(parseData: any, refMapList: I_DataRef) {
        let parseType_is = this.is(parseData);

        if (parseType_is.string) {
            let saveStrList = JSON.parse(parseData);
            if (this.is(saveStrList).Array) {
                //解析引用对象的类型
                refMapList._anyTypeMap = new Map(JSON.parse(saveStrList[2]));

                //构造第一层的引用元素
                let refMap = new Map<string, string>(JSON.parse(saveStrList[1]));
                refMap.forEach((refStr, guid) => {
                    let r_is = this.is(refStr);
                    if (r_is.string) {
                        let anyType = refMapList._anyTypeMap.get(guid);
                        // if (anyType) {

                        // }
                        let ref = JSON.parse(refStr);
                        let typeObj_is = this.transTypeToStr(anyType);
                        if (typeObj_is.map || typeObj_is.set) {
                            ref = new Map(ref);
                        }
                        refMapList._anyGuidMap.set(guid, ref);
                        refMapList._anyRefMap.set(ref, guid);
                    } else {
                        refMapList._anyGuidMap.set(guid, refStr);
                        refMapList._anyRefMap.set(refStr, guid);
                    }
                });

                /**解析引用 */
                let parseRef = (parseData: T_RefData | T_BaseData) => {
                    try {
                        let parseRef_is = this.is(parseData);
                        if (parseRef_is.string) {
                            //判断是是否是guid
                            let obj = refMapList._anyGuidMap.get(parseData as string);
                            if (obj) {
                                parseData = obj;
                            }
                        }
                        return parseData;
                    } catch (err) {
                        c3d.error(`转换出错err:`);
                        c3d.error(err);
                    }
                }

                //解析引用的子元素,替换guid为引用对象
                refMapList._anyGuidMap.forEach((ref, guid) => {
                    let anyType = refMapList._anyTypeMap.get(guid);
                    let typeObj_is = this.transTypeToStr(anyType);

                    //解析子元素
                    if (typeObj_is.map || typeObj_is.set) {
                        let refMap = ref as Map<string, any>;
                        for (let [k, childRef] of refMap) {
                            refMap.set(k, parseRef(childRef));
                        }
                    } else {
                        for (let [k, childRef] of Object.entries(ref)) {
                            ref[k] = parseRef(childRef);
                        }
                    }
                });

                //解析存储的部分
                parseData = parseRef(JSON.parse(saveStrList[0]));
            } else
                parseData = saveStrList;
        }

        //存储到解析map
        return parseData;
    }

    /**获取类型判断 */
    static is(val) {
        let type = this.getVarType(val);
        return this.transTypeToStr(type);
    }

    /**
        * 生成唯一的guid
        * @returns guid
        */
    static guid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0,
                v = c == 'x' ? r : (r & 0x3 | 0x8)
            return v.toString(16)
        })
    }

    /**类型转换 */
    static transTypeToStr(type: string) {
        return {
            object: type == 'Object',
            function: type == 'function',
            number: type == 'number',
            boolean: type == 'boolean',
            string: type == 'string',
            Array: type == 'Array',
            null: type == 'Null',
            NaN: type == 'NaN',
            date: type == 'Date',
            regExp: type == 'RegExp',
            error: type == 'Error',
            infinity: type == 'Infinity',
            undefined: type == 'undefined',
            map: type == 'Map',
            set: type == 'Set',
        }
    }

    /**
     * 判断类型
     * @param val 要判断的值
     * @returns 类型
     */
    static getVarType(val)
        : 'number' | 'string' | 'boolean' | 'Array' | 'Object' | 'function' | 'Null' |
        'Date' | 'RegExp' | 'Error' | 'NaN' | 'Infinity' | 'undefined' | "Map" | "Set" {
        var type = typeof val;
        // object需要使用Object.prototype.toString.call判断
        if (type === 'object') {
            var typeStr = Object.prototype.toString.call(val);
            // 解析[object String]
            typeStr = typeStr.split(' ')[1];
            type = typeStr.substring(0, typeStr.length - 1);
        } else if (type === 'number') {
            if (isNaN(val))
                return 'NaN';
            else if (Object.is(val, Infinity) || Object.is(val, -Infinity))
                return 'Infinity';
            else if (Object.is(val, undefined))
                return 'undefined';
        }
        return type as any;
    }

    private static _toBoolean(src, def) {
        if (typeof src == 'boolean') {
            return src;
        }
        else if (src == null || src == "") {
            return def;
        }
        else if (src == "false") {
            return false;
        }
        else if (src == "true") {
            return true;
        }
    }

    private static _toNumber(src, def) {
        let ret = Number(src);
        if (isNaN(ret)) {
            return def;
        }
        else {
            return ret;
        }
    }

    private static _toJSON(src, def) {
        try {
            let ret = JSON.parse(src);
            if (typeof ret == 'object' && ret) {
                return ret;
            }
            else {
                return def;
            }
        }
        catch (e) {
            console.error(`StorageUtil._toJSON：转换失败,error`, e, `,`, src, def);
            return def;
        }
    }


    public key = "VuxiAKihQ0VR9WRe";

    private encrypt(obj: {}) {
        return BitEncrypt.encode(JSON.stringify(obj), this.key);
    }

    private decryption(word: any) {
        return BitEncrypt.decode(word, this.key);
    }

    public getItem(key: string, defaultValue: any = null) {
        if (EDITOR) {
            return defaultValue;
        }
        let value = this.storage.getItem(key);
        if (value) {
            //解析
            try {
                let data = this.decryption(value);
                let result: StorageData = JSON.parse(data);
                if (result.type) {
                    return result.value;
                } else {
                    return value;
                }
            } catch (error) {
                return value;
            }
        }
        else {
            return defaultValue;
        }
    }

    public setItem(key: string, value: string | number | boolean | object) {
        if (EDITOR) {
            return;
        }
        let type = typeof value;
        if (type == "number" || type == "string" || type == "boolean" || type == "object") {
            let saveObj: StorageData = { type: type, value: value };
            //加密
            try {
                let data = this.encrypt(saveObj);
                this.storage.setItem(key, data);
            } catch (err) {
                if (DEBUG) c3d.error(err);
            }
        } else {
            if (DEBUG) c3d.error(`存储数据类型不支持 当前的存储类型: ${type}`);
        }
    }

    public removeItem(key: string) {
        if (EDITOR) return;
        this.storage.removeItem(key);
    }

    private get storage() {
        return window.localStorage;
    }
}